﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;
using Sustainalytics.Utils;

namespace Sustainalytics.DynamicsCRMGateway
{
    public enum WhereClauseOperator
    {
        None,
        Equal, 
        NotEqual,
        Or,
        And, 
        
    }
    public class WhereClause<T, TK>
    {
        private readonly Expression<Func<T, TK>> _property;
        private readonly TK _newValue;
        private readonly WhereClauseOperator _operator;
        private readonly WhereClauseOperator _nextOperator;

        public WhereClause(Expression<Func<T, TK>> property, WhereClauseOperator condtionOperator, TK newValue, WhereClauseOperator nextOperator = WhereClauseOperator.None)
        {
            _property = property;
            _newValue = newValue;
            _operator = condtionOperator;
            _nextOperator = nextOperator;
        }

        public Expression<Func<T, TK>> Property
        {
            get { return _property; }
        }

        public TK NewValue
        {
            get { return _newValue; }
        }
        public WhereClauseOperator Operator
        {
            get { return _operator; }
        }
        public WhereClauseOperator NextOperator
        {
            get { return _nextOperator; }
        }
    }

    public interface ICRMSimpleReader<T> : IDisposable where T : CrmEntity
    {
        T[] ReadNextPage();
        int Count { get; }
    }


    public class CRMSimpleReader<T> : ICRMSimpleReader<T> where T : CrmEntity
    {
        private readonly Dictionary<string, string> _dictionary = new Dictionary<string, string>();
        private readonly CrmOrganizationServiceContext _context;
        private readonly QueryExpression _query;
        private int _pageNumber;
        private string _entityName;



        public CRMSimpleReader(string connectionString, int pageSize, IEnumerable<Expression<Func<T, object>>> projections, IEnumerable<WhereClause<T, object>> filters)
        {
            MapLogicalNames();
            _context = new CrmOrganizationServiceContext(CrmConnection.Parse(connectionString));
            _context.TryAccessCache(cache => cache.Mode = OrganizationServiceCacheMode.Disabled);

            _query = new QueryExpression(_entityName);
            if (projections == null || !projections.Any())
            {
                _query.ColumnSet.AllColumns = true;
            }
            else
            {
                foreach (var expression in projections)
                    _query.ColumnSet.Columns.Add(_dictionary[expression.GetPropertyName()]);
            }

            if (filters != null && filters.Any())
            {
                _query.Criteria.FilterOperator = LogicalOperator.Or;
                foreach (var filter in filters)
                {
                    var logicalName = _dictionary[filter.Property.GetPropertyName()];
                    var Operator = filter.Operator == WhereClauseOperator.Equal? ConditionOperator.Equal: ConditionOperator.NotEqual;
                    var condition = new ConditionExpression(logicalName, Operator, filter.NewValue);
                    _query.Criteria.AddCondition(condition);
                }
            }

            _query.PageInfo.ReturnTotalRecordCount = true;
            _query.PageInfo.Count = pageSize;
            _pageNumber = 0;
        }

      
        public T[] ReadNextPage()
        {
            //lock (_entityName)
            {
                if (Done)
                    return null;

                _query.PageInfo.PageNumber = ++_pageNumber;
                var entitiesBatch = _context.RetrieveMultiple(_query);
                if (entitiesBatch != null)
                {
                    _query.PageInfo.PagingCookie = entitiesBatch.MoreRecords ? entitiesBatch.PagingCookie : null;
                    Count += entitiesBatch.Entities.Count;
                    Done = !entitiesBatch.MoreRecords;
                    return entitiesBatch.Entities.Cast<T>().ToArray();
                }
                return null;
            }
        }

        

        public int Count { get; private set; }
        private bool Done { get;  set; } 

        public void Dispose()
        {
            if (_context != null)
                _context.Dispose();
        }
        private void MapLogicalNames()
        {
            var eattr = Attribute.GetCustomAttribute(typeof(T), typeof(EntityLogicalNameAttribute)) as EntityLogicalNameAttribute;
            if (eattr != null)
                _entityName = eattr.LogicalName;


            var props = typeof(T).GetProperties();
            foreach (var prop in props)
            {

                var attrs = prop.GetCustomAttributes(true);
                foreach (object attr in attrs)
                {
                    var ln = attr as AttributeLogicalNameAttribute;
                    if (ln != null)
                    {
                        if (prop.PropertyType == typeof(CrmEntityReference))
                            _dictionary.Add(prop.Name + ".Id", ln.LogicalName);
                        else
                            _dictionary.Add(prop.Name, ln.LogicalName);
                            
                        
                        continue;
                    }
                }
            }




        }

    }
}
